﻿namespace Fluent.Metro.Behaviours;

using System.Diagnostics;
using System.Windows;
using Microsoft.Xaml.Behaviors;

/// <summary>
/// Enables the use of behaviors in styles
/// </summary>
public static class StylizedBehaviors
{
    /// <summary>
    /// <see cref="DependencyProperty"/> for behaviors.
    /// </summary>
    public static readonly DependencyProperty BehaviorsProperty
        = DependencyProperty.RegisterAttached("Behaviors",
            typeof(StylizedBehaviorCollection),
            typeof(StylizedBehaviors),
            new FrameworkPropertyMetadata(null, OnBehaviorsChanged));

    /// <summary>
    /// Gets the behaviors associated with <paramref name="dpo"/>
    /// </summary>
    public static StylizedBehaviorCollection? GetBehaviors(DependencyObject dpo)
    {
        return (StylizedBehaviorCollection?)dpo.GetValue(BehaviorsProperty);
    }

    /// <summary>
    /// Sets the behaviors associated with <paramref name="dpo"/>
    /// </summary>
    public static void SetBehaviors(DependencyObject dpo, StylizedBehaviorCollection? value)
    {
        dpo.SetValue(BehaviorsProperty, value);
    }

    private static void OnBehaviorsChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
    {
        var frameworkElement = dpo as FrameworkElement;
        if (frameworkElement is null)
        {
            return;
        }

        if (e.OldValue == e.NewValue)
        {
            return;
        }

        var itemBehaviors = Interaction.GetBehaviors(frameworkElement);

        frameworkElement.Unloaded -= FrameworkElementUnloaded;

        if (e.OldValue is StylizedBehaviorCollection oldBehaviors)
        {
            foreach (var behavior in oldBehaviors)
            {
                var index = GetIndexOf(itemBehaviors, behavior);
                if (index >= 0)
                {
                    itemBehaviors.RemoveAt(index);
                }
            }
        }

        if (e.NewValue is StylizedBehaviorCollection newBehaviors)
        {
            foreach (var behavior in newBehaviors)
            {
                var index = GetIndexOf(itemBehaviors, behavior);
                if (index < 0)
                {
                    var clone = (Behavior)behavior.Clone();
                    SetOriginalBehavior(clone, behavior);
                    itemBehaviors.Add(clone);
                }
            }
        }

        if (itemBehaviors.Count > 0)
        {
            frameworkElement.Unloaded += FrameworkElementUnloaded;
        }

        frameworkElement.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
    }

    private static void Dispatcher_ShutdownStarted(object? sender, System.EventArgs e)
    {
        Debug.WriteLine("Dispatcher.ShutdownStarted");
    }

    private static void FrameworkElementUnloaded(object sender, RoutedEventArgs e)
    {
        // BehaviorCollection doesn't call Detach, so we do this
        var frameworkElement = sender as FrameworkElement;
        if (frameworkElement is null)
        {
            return;
        }

        var itemBehaviors = Interaction.GetBehaviors(frameworkElement);

        foreach (var behavior in itemBehaviors)
        {
            behavior.Detach();
        }

        frameworkElement.Loaded += FrameworkElementLoaded;
    }

    private static void FrameworkElementLoaded(object sender, RoutedEventArgs e)
    {
        var frameworkElement = sender as FrameworkElement;
        if (frameworkElement is null)
        {
            return;
        }

        frameworkElement.Loaded -= FrameworkElementLoaded;
        var itemBehaviors = Interaction.GetBehaviors(frameworkElement);

        foreach (var behavior in itemBehaviors)
        {
            behavior.Attach(frameworkElement);
        }
    }

    private static int GetIndexOf(BehaviorCollection itemBehaviors, Behavior behavior)
    {
        var index = -1;

        var orignalBehavior = GetOriginalBehavior(behavior);

        for (var i = 0; i < itemBehaviors.Count; i++)
        {
            var currentBehavior = itemBehaviors[i];
            if (ReferenceEquals(currentBehavior, behavior)
                || ReferenceEquals(currentBehavior, orignalBehavior))
            {
                index = i;
                break;
            }

            var currentOrignalBehavior = GetOriginalBehavior(currentBehavior);
            if (ReferenceEquals(currentOrignalBehavior, behavior)
                || ReferenceEquals(currentOrignalBehavior, orignalBehavior))
            {
                index = i;
                break;
            }
        }

        return index;
    }

    // ReSharper disable once InconsistentNaming
    private static readonly DependencyProperty OriginalBehaviorProperty
        = DependencyProperty.RegisterAttached("OriginalBehavior",
            typeof(Behavior),
            typeof(StylizedBehaviors),
            new UIPropertyMetadata(null));

    private static Behavior? GetOriginalBehavior(DependencyObject obj)
    {
        return (Behavior)obj.GetValue(OriginalBehaviorProperty);
    }

    private static void SetOriginalBehavior(DependencyObject obj, Behavior? value)
    {
        obj.SetValue(OriginalBehaviorProperty, value);
    }
}